using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.IO;
using SpoonCore2.Client;
using SpoonCore2.Message;
using SpoonCore2.Message.Filter;
using SpoonCore2.Message.Header;
using SpoonCore2.Network;
using SpoonCore2.OverlayNetwork;
using System.Reflection;

namespace SpoonCore2.Service
{
	public class ServiceManager
	{
		private readonly SortedList<SpoonIdentifier, IService> _serviceList = new SortedList<SpoonIdentifier, IService>();
		private readonly ISpoonSocket _socket;
		private readonly IOverlayNetwork _network;
		private readonly MessageOptionManager _optionManager;
		private readonly MessageFilterManager _filterManager;

		public ServiceManager(ISpoonSocket socket, IOverlayNetwork network)
		{
			_optionManager = new MessageOptionManager(this);
			_filterManager = new MessageFilterManager();
			_socket = socket;
			_network = network;

			_socket.OnReceiveData += _socket_OnReceiveData;

			Register(network);
		}

		public IOverlayNetwork Network
		{
			get { return _network; }
		}

		public void Register(IService service)
		{
			if (!_serviceList.ContainsKey(service.Id))
			{
				_serviceList.Add(service.Id, service);
				service.OnSendData += service_OnSendData;
				service.OnSendDataInfo += service_OnSendDataInfo;
				service.OnSendDataEndpoint += service_OnSendDataEndpoint;
				service.SetManager(this);
				service.Init();
			}
		}

		public void LoadAll()
		{
			foreach (Type type in Assembly.GetCallingAssembly().GetTypes())
			{
				foreach (Type interf in type.GetInterfaces())
				{
					if (interf == typeof(IService) && !type.IsAbstract && !type.IsInterface) Register((IService)Activator.CreateInstance(type));
				}
			}
		}

		public T Get<T>() where T : IService, new()
		{
			return (T)Get(new T().Id);
		}

		public IService Get(SpoonIdentifier id)
		{
			if (_serviceList.ContainsKey(id)) return _serviceList[id];
			else throw new Exception("Service " + id + " not found");
		}

		private void service_OnSendData(IService sender, SpoonIdentifier id, IMessage message)
		{
			ClientInfo info = Network.GetClientInfo(id);
			if (info != null) service_OnSendDataInfo(sender, info, message);
		}

		private void service_OnSendDataInfo(IService sender, ClientInfo info, IMessage message)
		{
			if (!Network.Successor(info.Id))
				service_OnSendDataEndpoint(sender, info.RandomEndpoint, message);
			else
			{
				MemoryStream stream = new MemoryStream();

				BinaryWriter bw = new BinaryWriter(stream);
				bw.Write(message.GetType().FullName.GetHashCode());
				message.GetBytes(bw);

				stream.Position = 0;
				sender.Receive(Network.ClientInfo, new BinaryReader(stream), message.Options, message.Filters);
			}
		}

		#region Send/Receive
		private void service_OnSendDataEndpoint(IService sender, IPEndPoint endpoint, IMessage message)
		{
			MemoryStream stream = new MemoryStream();
			BinaryWriter bw = new BinaryWriter(stream);
			bw.Write(sender.Id.GetBytes());
			Network.ClientInfo.GetBytes(bw);
			_filterManager.GetBytes(bw, message.Filters);

			MemoryStream streamData = new MemoryStream();
			BinaryWriter bwData = new BinaryWriter(streamData);
			_optionManager.GetBytes(bwData, message);
			bwData.Write(message.GetType().FullName.GetHashCode());
			message.GetBytes(bwData);

			byte[] data = streamData.ToArray();
			_filterManager.Encode(message.Filters, ref data);
			bw.Write(data.Length);
			bw.Write(data);

			_socket.Send(endpoint, stream.ToArray());
		}

		private void _socket_OnReceiveData(IPEndPoint endpoint, BinaryReader br)
		{
			SpoonIdentifier sId = new SpoonIdentifier(br);
			if (_serviceList.ContainsKey(sId))
			{
				ClientInfo senderInfo = new ClientInfo(_serviceList[sId], br);
				senderInfo.AddEndpoint(endpoint);
				Network.AddClient(senderInfo);

				IList<IMessageFilterInfo> filters = _filterManager.SetBytes(br);

				int count = br.ReadInt32();
				byte[] data = br.ReadBytes(count);
				_filterManager.Decode(filters, ref data);

				BinaryReader brData = new BinaryReader(new MemoryStream(data));
				IList<IMessageOption> options = _optionManager.SetBytes(brData);
				
				_serviceList[sId].Receive(senderInfo, brData, options, filters);
			}
			//TODO routing
		}
		#endregion
	}
}